package org.opensource.clearpool.jta; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.opensource.clearpool.exception.TransactionException; import org.opensource.clearpool.jta.xa.XidImpl; import org.opensource.clearpool.logging.PoolLogger; import org.opensource.clearpool.logging.PoolLoggerFactory; /** * This class used to control jta transaction. * * @author xionghui * @date 16.08.2014 * @version 1.0 */ class TransactionImpl implements Transaction { private static final PoolLogger LOGGER = PoolLoggerFactory.getLogger(TransactionImpl.class); private int status = Status.STATUS_ACTIVE; private boolean rollbackOnly; private int seconds; private List<Synchronization> synList; private Map<XAResource, Xid> xaResMap = new HashMap<XAResource, Xid>(); private List<ResourceCarry> resList = new ArrayList<ResourceCarry>(); TransactionImpl(int seconds) { this.seconds = seconds; } public void setTransactionTimeout(int seconds) { this.seconds = seconds; } @Override public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, SystemException { if (status == Status.STATUS_ROLLEDBACK) { throw new RollbackException("the transaction had been rolled back"); } if (rollbackOnly) { this.rollback(); return; } if (status != Status.STATUS_ACTIVE) { throw new IllegalStateException("the transaction is not active"); } this.tryEndResource(); boolean preparedSuccess = this.tryPrepared(); if (preparedSuccess) { this.tryCommit(); return; } try { this.tryRollback(); } catch (SystemException e) { throw new HeuristicRollbackException(e.getMessage()); } throw new HeuristicRollbackException("roll back all the transaction"); } /** * Try to end XA Resource */ private void tryEndResource() { for (ResourceCarry carry : resList) { try { carry.xaRes.end(carry.xid, XAResource.TMSUCCESS); } catch (XAException e) { LOGGER.error("can't end XA: (error code = " + e.errorCode + "): ", e); } } } /** * Try to prepared */ private boolean tryPrepared() { status = Status.STATUS_PREPARING; boolean preparedSuccess = true; for (ResourceCarry carry : resList) { try { int result = carry.xaRes.prepare(carry.xid); preparedSuccess &= result == XAResource.XA_OK; } catch (XAException e) { LOGGER.error("can't prepare XA: (error code = " + e.errorCode + "): ", e); preparedSuccess = false; } } status = Status.STATUS_PREPARED; return preparedSuccess; } /** * Try to commit */ private void tryCommit() throws HeuristicMixedException, SystemException { status = Status.STATUS_COMMITTING; ResourceCarry carryTemp = null; Iterator<ResourceCarry> itr = resList.iterator(); while (itr.hasNext()) { ResourceCarry carry = itr.next(); this.beforeCompletion(); try { carry.xaRes.commit(carry.xid, false); } catch (XAException e) { LOGGER.error("can't commit XA: (error code = " + e.errorCode + "): ", e); carryTemp = carry; break; } this.afterCompletion(Status.STATUS_COMMITTED); } if (carryTemp == null) { status = Status.STATUS_COMMITTED; return; } this.rollbackMixed(carryTemp, itr); } /** * roll back Mixed */ private void rollbackMixed(ResourceCarry carryTemp, Iterator<ResourceCarry> itr) throws HeuristicMixedException, SystemException { int st = Status.STATUS_ROLLEDBACK; status = Status.STATUS_ROLLEDBACK; this.beforeCompletion(); try { carryTemp.xaRes.rollback(carryTemp.xid); } catch (XAException e) { LOGGER.error("can't roll back XA: (error code = " + e.errorCode + "): ", e); st = Status.STATUS_UNKNOWN; } this.afterCompletion(st); while (itr.hasNext()) { ResourceCarry carry = itr.next(); this.beforeCompletion(); st = Status.STATUS_ROLLEDBACK; try { carry.xaRes.rollback(carry.xid); } catch (XAException e) { LOGGER.error("can't roll back XA: (error code = " + e.errorCode + "): ", e); st = Status.STATUS_UNKNOWN; } this.afterCompletion(st); } throw new HeuristicMixedException("roll back some transaction"); } /** * Invoke before the second phase */ private void beforeCompletion() { if (synList == null) { return; } for (Synchronization syn : synList) { syn.beforeCompletion(); } } /** * Invoke after the second phase */ private void afterCompletion(int st) { if (synList == null) { return; } for (Synchronization syn : synList) { syn.afterCompletion(st); } } /** * Try to roll back */ private void tryRollback() throws SystemException { status = Status.STATUS_ROLLING_BACK; StringBuilder error = new StringBuilder(); for (ResourceCarry carry : resList) { try { carry.xaRes.rollback(carry.xid); } catch (XAException e) { LOGGER.error("tryRollback error: ", e); try { carry.xaRes.forget(carry.xid); } catch (XAException ex) { error.append("can't roll back XAException: "); error.append(ex); error.append(" (error code = "); error.append(ex.errorCode); error.append(") "); error.append(ex.getMessage()); error.append("\n"); } } } status = Status.STATUS_ROLLEDBACK; if (error.length() > 0) { error.deleteCharAt(error.length() - 1); throw new SystemException(error.toString()); } } @Override public boolean delistResource(XAResource xaresource, int i) throws SystemException { if (status != Status.STATUS_ACTIVE) { throw new IllegalStateException("the transaction is not active"); } TransactionAdapter txAdapt = (TransactionAdapter) TransactionManagerImpl.getManager().getTransaction(); if (txAdapt.getTx() != this) { throw new IllegalStateException("the transaction is not held"); } Xid xid = xaResMap.get(xaresource); if (xid == null) { return false; } try { xaresource.end(xid, i); } catch (XAException e) { String error = "can't end XA: " + e + " (error code = " + e.errorCode + ") " + e.getMessage(); throw new SystemException(error); } return true; } @Override public boolean enlistResource(XAResource xaresource) throws RollbackException, SystemException { if (status != Status.STATUS_ACTIVE) { throw new IllegalStateException("the transaction is not active"); } if (rollbackOnly) { throw new RollbackException("the transaction is signed to roll back only"); } TransactionAdapter txAdapt = (TransactionAdapter) TransactionManagerImpl.getManager().getTransaction(); if (txAdapt.getTx() != this) { throw new IllegalStateException("the transaction is not held"); } try { if (seconds != 0) { boolean result = xaresource.setTransactionTimeout(seconds); // result is false when XAResource does not support setTransactionTimeout if (!result) { throw new TransactionException("XAResource setTransactionTimeout false."); } } if (!xaResMap.containsKey(xaresource)) { ResourceCarry carry = new ResourceCarry(xaresource); xaresource.start(carry.xid, XAResource.TMNOFLAGS); resList.add(carry); xaResMap.put(xaresource, carry.xid); } } catch (XAException e) { LOGGER.error("can't start XA: (error code = " + e.errorCode + "): ", e); return false; } return true; } @Override public int getStatus() throws SystemException { return status; } @Override public void registerSynchronization(Synchronization synchronization) throws RollbackException, SystemException { if (status != Status.STATUS_ACTIVE) { throw new IllegalStateException("the transaction is not active"); } if (rollbackOnly) { throw new RollbackException("the transaction is signed to roll back only"); } TransactionAdapter txAdapt = (TransactionAdapter) TransactionManagerImpl.getManager().getTransaction(); if (txAdapt.getTx() != this) { throw new IllegalStateException("the transaction is not held"); } if (synList == null) { synList = new ArrayList<Synchronization>(); } synList.add(synchronization); } @Override public void rollback() throws SystemException { if (status != Status.STATUS_ACTIVE && status != Status.STATUS_MARKED_ROLLBACK) { throw new IllegalStateException("the transaction is not active"); } this.tryEndResource(); this.tryRollback(); } @Override public void setRollbackOnly() throws SystemException { rollbackOnly = true; status = Status.STATUS_MARKED_ROLLBACK; } void suspend() { for (ResourceCarry carry : resList) { try { carry.xaRes.end(carry.xid, XAResource.TMSUSPEND); } catch (XAException e) { LOGGER.error("can't end XA: (error code = " + e.errorCode + "): ", e); } } } void resume() { for (ResourceCarry carry : resList) { try { carry.xaRes.start(carry.xid, XAResource.TMRESUME); } catch (XAException e) { LOGGER.error("can't end XA: (error code = " + e.errorCode + "): ", e); } } } /** * The resource and xid carrier. * * @author xionghui * @date 16.08.2014 * @version 1.0 */ private static class ResourceCarry { Xid xid; XAResource xaRes; ResourceCarry(XAResource xaRes) { xid = new XidImpl(); this.xaRes = xaRes; } } }